require 'inch/language/ruby/provider/yard/object/method_signature'

module Inch
  module Language
    module Ruby
      module Provider
        module YARD
          module Object
            # Proxy class for methods
            class MethodObject < Base
              UNUSABLE_RETURN_VALUES = %w(nil nothing undefined void)

              def aliases_fullnames
                object.aliases.map(&:path)
              end

              def bang_name?
                name =~ /\!$/
              end

              def constructor?
                name == :initialize
              end

              def getter?
                attr_info = object.attr_info || {}
                read_info = attr_info[:read]
                if read_info
                  read_info.path == fullname
                else
                  parent.child(:"#{name}=")
                end
              end

              def has_code_example?
                signatures.any? { |s| s.has_code_example? }
              end

              def has_doc?
                signatures.any? { |s| s.has_doc? }
              end

              def method?
                true
              end

              def parameters
                @parameters ||= signatures.map(&:parameters).flatten
              end

              def parameter(name)
                parameters.find { |p| p.name == name.to_s }
              end

              # Returns the original docstring unless it was generated by YARD.
              # @return [String]
              def original_docstring
                implicit_docstring? ? "" : super
              end

              def overridden?
                !!object.overridden_method
              end

              def overridden_method
                return unless overridden?
                @overridden_method ||= YARD::Object.for(object.overridden_method)
              end

              def overridden_method_fullname
                return unless overridden?
                overridden_method.fullname
              end

              # Returns +true+ if a return value is described by it's type or
              # mentioned in the docstring (e.g. "Returns a String").
              def return_mentioned?
                return_tags.any? do |t|
                  !t.types.nil? && !t.types.empty? &&
                    !YARD.implicit_tag?(t, self)
                end || docstring.mentions_return? && !implicit_docstring?
              end

              # Returns +true+ if a return value is described by words.
              def return_described?
                return_described_via_tag? ||
                  docstring.describes_return? && !implicit_docstring?
              end

              def return_typed?
                return_mentioned?
              end

              def setter?
                name =~ /\=$/ && parameters.size == 1
              end

              def signatures
                base = MethodSignature.new(self, nil)
                overloaded = overload_tags.map do |tag|
                  MethodSignature.new(self, tag)
                end
                if overloaded.any? { |s| s.same?(base) }
                  overloaded
                else
                  [base] + overloaded
                end
              end

              def questioning_name?
                name =~ /\?$/
              end

              private

              # Returns @return tags that are assigned to the getter
              # corresponding to this setter.
              #
              # @return [Array<::YARD::Tag>]
              def attributed_return_tags
                if setter? && object.tags(:return).empty?
                  method = corresponding_getter
                  return method.object.tags(:return) if method
                end
                []
              end

              # @return [MethodObject,nil]
              def corresponding_getter
                clean_name = name.to_s.gsub(/(\=)$/, '')
                parent.child(clean_name.to_sym)
              end

              # Returns +true+ if the docstring was generated by YARD.
              def implicit_docstring?
                YARD.implicit_docstring?(docstring, self)
              end

              # @return [Array<::YARD::Tag>]
              def overload_tags
                object.tags(:overload)
              end

              # @return [Array<::YARD::Tag>]
              def overloaded_return_tags
                overload_tags.map do |overload_tag|
                  overload_tag.tag(:return)
                end.compact
              end

              # @return [Array<::YARD::Tag>]
              def return_tags
                object.tags(:return) +
                  overloaded_return_tags +
                    attributed_return_tags
              end

              # Returns +true+ if a return value is described via tags.
              def return_described_via_tag?
                return_tags.any? do |t|
                  return_tag_describes_unusable_value?(t) ||
                    !t.text.to_s.empty? && !YARD.implicit_tag?(t, self)
                end
              end

              def return_tag_describes_unusable_value?(t)
                return false if t.types.nil?
                t.types.size == 1 &&
                  UNUSABLE_RETURN_VALUES.include?(t.types.first)
              end
            end
          end
        end
      end
    end
  end
end
